﻿using System.Collections.Generic;
using System;
using System.IO;
using System.Xml;
using System.Text;
using PDOGenerator.MapTypes;
using System.Data.OleDb;

namespace PDOGenerator.Generators
{
    class MasterGenerator
    {

        public static GeneratorPM PM
        {
            get { return (PdoApp.Current as PdoApp).PM; }
        }

        /// <summary>
        /// Generate all output class and script files.
        /// </summary>
        /// <param name="p">project</param>
        public static void WriteFiles(Project p)
        {
            if (!Directory.Exists(p.OutputDirectory))
            {
                Directory.CreateDirectory(p.OutputDirectory);
            }

            string projPath = p.OutputDirectory + p.Assembly + ".csproj";
            CreateOrUpdateProject(projPath, p);

            foreach (TableMap t in p.AllTables.Values)
            {
                foreach (ColumnMap c in t.Columns.Values)
                {
                    if (!c.IsCollection)
                    {
                        ToPDOClass.SetColumnInferredProperties(c);
                    }
                }
            }


            // Build up subproject list
            List<string> subProjectList = new List<string>();
            foreach (SchemaMap sm in p.SchemaList)
            {
                if (!subProjectList.Contains(sm.SubProjectName))
                {
                    subProjectList.Add(sm.SubProjectName);
                }
            }


            // Classses
            if (p.GenerateClassFiles)
            {
                ClassGenerator gen = ClassGenerator.GetCodeGenerator(p);

                foreach (TableMap t in p.AllTables.Values)
                {
                    if (!t.IsGeneratable)
                    {
                        PM.BusyMessage = "Generating Class file: " + t.ClassName;

                        string filePath = Path.Combine(p.OutputDirectory, t.ClassFileName);

                        string path = Path.GetDirectoryName(filePath);
                        if (!Directory.Exists(path))
                        {
                            Directory.CreateDirectory(path);
                        }

                        File.WriteAllText(filePath, gen.Generate(t), Encoding.Unicode);
                    }
                }
            }



            // Stored Procs
            if (p.GenerateProcScripts)
            {
                ClassGenerator gen = ClassGenerator.GetStoredProcGenerator(p);

                foreach (string subProjectName in subProjectList)
                {
                    PM.BusyMessage = "Generating CRUD Procs for: " + p.ProjectName + CoalescePrefix(subProjectName, ".");

                    bool initialized = false;
                    foreach (SchemaMap s in p.SchemaList)
                    {
                        if (subProjectName == s.SubProjectName)
                        {
                            string filePath = PrepareScriptOutputFile(p, s, "CRUD" + CoalescePrefix(subProjectName) + ".sql", initialized);
                            initialized = true;

                            foreach (TableMap t in s.Tables.Values)
                            {
                                if (!t.IsGeneratable)
                                {
                                    File.AppendAllText(filePath, gen.Generate(t));
                                }
                            }
                        }
                    }
                }
            }

            // Table create and drop scripts
            OleDbConnectionStringBuilder scb = new OleDbConnectionStringBuilder(p.ConnectionString);
            SQLScriptGenerator sg = new SQLScriptGenerator(scb.DataSource, scb["Initial Catalog"].ToString(), p.TargetAzure); 


            // Table create
            if (p.GenerateTableCreateScripts)
            {
                // First, ensure all necessary schemas are created prior to table create.
                foreach (string subProjectName in subProjectList)
                {
                    string filePath = PrepareScriptOutputFile(p, null, "TableCreate" + CoalescePrefix(subProjectName) + ".sql", false);
                    File.AppendAllText(filePath, sg.GenerateSubProjectSchemaScript(p, subProjectName) + Environment.NewLine);
                }

                // create all tables 
                foreach (string subProjectName in subProjectList)
                {
                    PM.BusyMessage = "Generating Create Procs for: " + p.ProjectName + CoalescePrefix(subProjectName, ".");

                    string filePath = PrepareScriptOutputFile(p, null, "TableCreate" + CoalescePrefix(subProjectName) + ".sql", true);
                    File.AppendAllText(filePath, sg.GenerateSubProjectTableScript(p, subProjectName, false) + Environment.NewLine);
                }

                if (p.GenerateDataInsertScripts)
                {
                    // create data insert scripts
                    foreach (string subProjectName in subProjectList)
                    {
                        PM.BusyMessage = "Generating Data scripts for: " + p.ProjectName + CoalescePrefix(subProjectName, ".");

                        string filePath = PrepareScriptOutputFile(p, null, "DefaultData" + CoalescePrefix(subProjectName) + ".sql", false);
                        File.AppendAllText(filePath, sg.GenereateDataScript(p, subProjectName) + Environment.NewLine);
                    }
                }

            }

            // Table drop
            if (p.GenerateTableDropScripts)
            {
                // drop all tables
                foreach (string subProjectName in subProjectList)
                {
                    PM.BusyMessage = "Generating Drop Procs for: " + p.ProjectName + CoalescePrefix(subProjectName, ".");
                    string filePath = PrepareScriptOutputFile(p, null, "TableDrop" + CoalescePrefix(subProjectName) + ".sql", false);

                    File.AppendAllText(filePath, sg.GenerateSubProjectTableScript(p, subProjectName, true) + Environment.NewLine);
                }
            }

        }


        private static string CoalescePrefix(string subProjectName)
        {
            return CoalescePrefix(subProjectName, "_");

        }
        private static string CoalescePrefix(string subProjectName, string prefix)
        {
            string token = string.Empty;

            if (!string.IsNullOrEmpty(subProjectName))
            {
                token = prefix + subProjectName;
            }
            return token;
        }

        private static string PrepareScriptOutputFile(Project project, SchemaMap schemaMap, string fileSuffix, bool initialized)
        {
            string filePath = null;
            string outDir = null;
            const string defaultScriptOutputDir = "Scripts";



            //----------------------------------------------------------------------------
            // TESTING - IN PROGRESS
            if (null != schemaMap)
            {

                // CX - need to re-org subproject / schema relationships...
                // Build output path from project output directory and (optional) script output directory.
                string scriptOutputDir = schemaMap.ScriptOutputDirectory ?? defaultScriptOutputDir;

                if (null != schemaMap.ScriptOutputDirectory)
                {
                    int foo = 2;
                }

                if (Path.IsPathRooted(scriptOutputDir))
                {
                    outDir = scriptOutputDir;
                }
                else
                {
                    outDir = Path.Combine(project.OutputDirectory, scriptOutputDir);
                }
            }
            //----------------------------------------------------------------------------




            // Build output path from project output directory.
            outDir = Path.Combine(project.OutputDirectory, defaultScriptOutputDir);
            filePath = Path.Combine(outDir, fileSuffix);

            // Check regardless of initialized value - will be different for each schema if not combining scripts.
            if (!Directory.Exists(outDir))
            {
                Directory.CreateDirectory(outDir);
            }

            if (!initialized)
            {
                File.Delete(filePath);
            }

            return filePath;
        }

        private static void CreateOrUpdateProject(string ProjPath, Project p)
        {
            if (!File.Exists(ProjPath))
            {
                CreateEmptyProject(ProjPath, p);
            }

            // read into an XML tree
            XmlDocument doc = new XmlDocument();
            doc.Load(ProjPath);

            foreach (TableMap t in p.AllTables.Values)
            {
                if (!t.CreateClass && !t.CreateEnum)
                {
                    continue;
                }
                if (!t.HasPrimaryKey())
                {
                    continue;
                }

                InsertClassNode(doc.ChildNodes[0], t);
                InsertXmlNode(doc.ChildNodes[0], t);
            }

            // and save the finished file
            doc.Save(ProjPath);
        }

        private static void InsertXmlNode(XmlNode doc, TableMap t)
        {
            if (NodeExists(doc, t.XmlFileName))
            {
                return;
            }

            XmlNode IGNode = FindItemGroupNode(doc, "EmbeddedResource");

            XmlNode ClassNode = doc.OwnerDocument.CreateNode(XmlNodeType.Element, "EmbeddedResource", doc.NamespaceURI);
            ClassNode.Attributes.Append(doc.OwnerDocument.CreateAttribute("Include"));
            ClassNode.Attributes["Include"].Value = t.XmlFileName;

            IGNode.AppendChild(ClassNode);
        }

        private static XmlNode IGNode = null;
        private static XmlNode FindItemGroupNode(XmlNode doc, string p)
        {
            if (IGNode != null)
            {
                return IGNode;
            }

            XmlNode PossibleNode = null;

            foreach (XmlNode GroupNode in doc.ChildNodes)
            {
                foreach (XmlNode DetailNode in GroupNode.ChildNodes)
                {
                    if (DetailNode.Name == p)
                    {
                        return GroupNode;
                    }

                    if (DetailNode.Name == "EmbeddedResource" || DetailNode.Name == "Compile")
                    {
                        PossibleNode = GroupNode;
                    }
                }
            }

            if (PossibleNode != null)
            {
                return PossibleNode;
            }

            IGNode = doc.OwnerDocument.CreateNode(XmlNodeType.Element, "ItemGroup", doc.NamespaceURI);
            doc.AppendChild(IGNode);

            return IGNode;
        }

        private static bool NodeExists(XmlNode doc, string p)
        {
            foreach (XmlNode GroupNode in doc.ChildNodes)
            {
                foreach (XmlNode DetailNode in GroupNode.ChildNodes)
                {
                    if (DetailNode.Attributes["Include"] != null)
                    {
                        if (DetailNode.Attributes["Include"].Value == p)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        private static void InsertClassNode(XmlNode doc, TableMap t)
        {
            if (NodeExists(doc, t.ClassFileName))
            {
                return;
            }

            XmlNode IGNode = FindItemGroupNode(doc, "Compile");

            XmlNode ClassNode = doc.OwnerDocument.CreateNode(XmlNodeType.Element, "Compile", doc.NamespaceURI);
            ClassNode.Attributes.Append(doc.OwnerDocument.CreateAttribute("Include"));
            ClassNode.Attributes["Include"].Value = t.ClassFileName;

            IGNode.AppendChild(ClassNode);
        }

        private static void CreateEmptyProject(string ProjPath, Project p)
        {
            // create an empty project file

            string s = @"
<Project DefaultTargets=&Q;Build&Q; xmlns=&Q;http://schemas.microsoft.com/developer/msbuild/2003&Q;>
  <PropertyGroup>
    <Configuration Condition=&Q; '$(Configuration)' == '' &Q;>Debug</Configuration>
    <Platform Condition=&Q; '$(Platform)' == '' &Q;>AnyCPU</Platform>
    <ProductVersion>8.0.50727</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <OutputType>Library</OutputType>
    <RootNamespace>" + p.NameSpace + @"</RootNamespace>
    <AssemblyName>" + p.Assembly + @"</AssemblyName>
  </PropertyGroup>
  <PropertyGroup Condition=&Q; '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' &Q;>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
  <PropertyGroup Condition=&Q; '$(Configuration)|$(Platform)' == 'Release|AnyCPU' &Q;>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
    <ItemGroup>
    <Reference Include=&Q;System&Q;>
      <Name>System</Name>
    </Reference>
  </ItemGroup>
  <Import Project=&Q;$(MSBuildBinPath)\Microsoft.CSharp.targets&Q; />
  <PropertyGroup>
    <PreBuildEvent>
    </PreBuildEvent>
    <PostBuildEvent>
    </PostBuildEvent>
  </PropertyGroup>
</Project>
";
            File.WriteAllText(ProjPath, s.Replace("&Q;", "\""));
        }
    }
}
